﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Globalization;
using System.Collections.ObjectModel;

namespace TribesPSO
{
    /// <summary>
    /// A euclidian vector is an immutable n-dimensional vector used to represent a point in N dimensional space
    /// </summary>
    public sealed class EuclidianVector
    {
        /// <summary>
        /// The number of dimensions that the vector occupies
        /// </summary>
        public int Dimensions { get { return this.dimensionData.Count(); } }
        private readonly List<double> dimensionData;

        /// <summary>
        /// Initializes a new EuclidianVector class with the specified dimension data
        /// </summary>
        /// <param name="data">The dimension data that makes up the EuclidianVector</param>
        public EuclidianVector(params double[] data)
        {
            this.dimensionData = new List<double>(data.AsEnumerable());
        }

        /// <summary>
        /// Initializes a new EuclidianVector class with the specified dimension data
        /// </summary>
        /// <param name="dimensionData">The dimension data that makes up the EuclidianVector</param>
        public EuclidianVector(IEnumerable<double> dimensionData)
        {
            this.dimensionData = new List<double>(dimensionData);
        }

        /// <summary>
        /// Private constructor that gets used when a private list of dimensions has already been constructed.
        /// This saves a call to the List constructor that would get called if the public constructor that takes an
        /// IEnumerable is called
        /// </summary>
        /// <param name="dimensionData"></param>
        private EuclidianVector(List<double> dimensionData)
        {
            this.dimensionData = dimensionData;
        }

        /// <summary>
        /// Internal method that's intended to be called by the EuclidianVectorBuilder class.
        /// </summary>
        /// <param name="dimensionData"></param>
        /// <returns></returns>
        internal static EuclidianVector EuclidianVectorFactory(List<double> dimensionData)
        {
            //Calls the private constructor that doesn't do a copy operation
            return new EuclidianVector(dimensionData);
        }

        /// <summary>
        /// Computes the magnitude of the vector
        /// </summary>
        public double Length
        {
            get
            {
                double agrigator = 0;
                for (int n = 0; n < this.Dimensions; n++)
                {
                    agrigator += Math.Pow(this[n], 2);
                }
                return Math.Sqrt(agrigator);
            }
        }

        /// <summary>
        /// Gets the specified dimension data
        /// </summary>
        /// <param name="index">The dimension to get</param>
        /// <returns>The magnitude of the data in the specified dimension</returns>
        public double this[int index]
        {
            get
            {
                return this.dimensionData[index];
            }
        }

        private ReadOnlyCollection<double> readOnlyData;

        /// <summary>
        /// Gets a the dimension data of the euclidian vector
        /// </summary>
        public IList<double> DimensionData
        {
            get
            {
                /*The first time this is called, create a new ReadOnlyCollection.  On subsequent calls
                 * return the same collection*/

                //Note, I thought about locking on readOnlyData here, but in reality: who cares?  The underlying
                //data is immutable so the worst case scenario is that more than one ReadOnlyCollection gets constructed
                //IF this race condition is hit (which is already unlikely)
                if (this.readOnlyData == null) this.readOnlyData = dimensionData.AsReadOnly();
                return this.readOnlyData;
            }
        }

        /// <summary>
        /// Subtracts N-Dimensional vector b from vector a.  a and b must have the same number of dimensions
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns>A new euclidian vector represnting the difference between vector b and a</returns>
        public static EuclidianVector operator -(EuclidianVector a, EuclidianVector b)
        {
            return EuclidianVector.Subtract(a, b);
        }

        /// <summary>
        /// Subtract N-Dimensional vector b from a.  a and b must have the same number of dimensions
        /// </summary>
        /// <param name="a">The vector to subtract from</param>
        /// <param name="b">The vector to subtract</param>
        /// <returns>a-b</returns>
        public static EuclidianVector Subtract(EuclidianVector a, EuclidianVector b)
        {
            if (ReferenceEquals(a, null)) throw new ArgumentNullException("a");
            if (ReferenceEquals(b, null)) throw new ArgumentNullException("b");
            if (a.Dimensions != b.Dimensions)
            {
                throw new DimensionMismatchException(
                    string.Format(
                        CultureInfo.InvariantCulture,
                        "Cannot subtract a {0} dimensional vector from a {1} dimensional vector",
                        a.Dimensions,
                        b.Dimensions
                        )
                    );
            }
            var difference = a.dimensionData.Zip(b.dimensionData, (aVal, bVal) => aVal - bVal);
            return new EuclidianVector(difference);
        }

        /// <summary>
        /// Adds N-Dimensional vector a to b.  a and b must have the same number of dimensions
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns>The sum of vector a and vector b</returns>
        public static EuclidianVector operator +(EuclidianVector a, EuclidianVector b)
        {
            return EuclidianVector.Add(a, b);
        }

        /// <summary>
        /// Add N-Dimensional vector a to b.  a and b must be the same length
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns>a+b</returns>
        /// <exception cref="DimensionMismatchException">Thrown if the dimensions of a don't match the dimensions of b.
        /// IE you can't add a 2 dimensional vector to a 3 dimensional vector</exception>
        public static EuclidianVector Add(EuclidianVector a, EuclidianVector b)
        {
            if (ReferenceEquals(a, null)) throw new ArgumentNullException("a");
            if (ReferenceEquals(b, null)) throw new ArgumentNullException("b");
            if (a.Dimensions != b.Dimensions)
            {
                throw new DimensionMismatchException(
                    string.Format(
                        CultureInfo.InvariantCulture,
                        "Cannot add a {0} dimensional vector to a {1} dimensional vector",
                        a.Dimensions,
                        b.Dimensions
                        )
                    );
            }
            var sum = a.dimensionData.Zip(b.dimensionData, (aVal, bVal) => aVal + bVal);
            return new EuclidianVector(sum);
        }

        /// <summary>
        /// Divides vector a by a scalar value
        /// </summary>
        /// <param name="a"></param>
        /// <param name="divisor"></param>
        /// <returns>Vector a, length scaled by the divisor</returns>
        public static EuclidianVector operator /(EuclidianVector a, double divisor)
        {
            return EuclidianVector.Divide(a, divisor);
        }

        /// <summary>
        /// Perform scalar division on N-Dimensional vector a
        /// </summary>
        /// <param name="a">The vector to divide</param>
        /// <param name="divisor">The scalar to divide by</param>
        /// <returns>a / divisor</returns>
        public static EuclidianVector Divide(EuclidianVector a, double divisor)
        {
            if (a == null) throw new ArgumentNullException("a");
            var NewPoints = from dimension in a.dimensionData
                            select dimension / divisor;
            return new EuclidianVector(NewPoints);
        }

        /// <summary>
        /// Multiplies vector a by a scalar value
        /// </summary>
        /// <param name="a"></param>
        /// <param name="scalar"></param>
        /// <returns>A new vector with the same direction as vector a, length scaled by scalar</returns>
        public static EuclidianVector operator *(EuclidianVector a, double scalar)
        {
            return EuclidianVector.Multiply(a, scalar);
        }

        /// <summary>
        /// Perform scalar multiplication on N-Dimensional vector a
        /// </summary>
        /// <param name="a">The vector to multiply</param>
        /// <param name="scalar">The scalar to multiply by</param>
        /// <returns></returns>
        public static EuclidianVector Multiply(EuclidianVector a, double scalar)
        {
            if (a == null) throw new ArgumentNullException("a");
            var NewPoints = from dimension in a.dimensionData
                            select dimension * scalar;
            return new EuclidianVector(NewPoints);
        }

        /// <summary>
        /// Returns an N-Dimensional representation of the origion. {0,0} in 2 dimensions.  {0,0,0} in 3 dimensions etc.
        /// </summary>
        /// <param name="dimensions">The number of dimensions in the returned vector</param>
        /// <returns>An N-Dimensional vector with 0 length</returns>
        public static EuclidianVector Origin(int dimensions)
        {
            List<double> Zeroes = new List<double>();
            for (int n = 0; n < dimensions; n++)
            {
                Zeroes.Add(0);
            }
            return new EuclidianVector(Zeroes);
        }

        /// <summary>
        /// Computes an N-dimensional vector pointing two the center of Point1 and Point2.
        /// The same as calling CenterOfGravity(Point1, 1, Point2, 1);
        /// </summary>
        /// <param name="point1"></param>
        /// <param name="point2"></param>
        /// <returns></returns>
        public static EuclidianVector CenterOfGravity(EuclidianVector point1, EuclidianVector point2)
        {
            return EuclidianVector.CenterOfGravity(point1, 1, point2, 1);
        }

        /// <summary>
        /// Computes an N-dimensional vector pointing to the weighted center of Point1 and Point2.  The greater the mass
        /// of Point1 compared to the mass of Point2, the closer the result will be to Point1 and vice versa
        /// </summary>
        /// <remarks>The mass of both particles cannot be 0</remarks>
        /// <param name="point1"></param>
        /// <param name="point1Mass"></param>
        /// <param name="point2"></param>
        /// <param name="point2Mass"></param>
        /// <returns>A point representing the center of mass between the 2 particles</returns>
        public static EuclidianVector CenterOfGravity(EuclidianVector point1, double point1Mass, EuclidianVector point2, double point2Mass)
        {
            if (ReferenceEquals(point1, null)) throw new ArgumentNullException("point1");
            if (ReferenceEquals(point2, null)) throw new ArgumentNullException("point2");
            if (point1.Dimensions != point2.Dimensions)
            {
                throw new DimensionMismatchException(
                    string.Format(
                        CultureInfo.InvariantCulture,
                        "Cannot compute the center of mass between a {0} dimensional vector and a {1} dimensional vector",
                        point1.Dimensions,
                        point2.Dimensions
                        )
                    );
            }
            var NewPoints = point1.dimensionData.Zip(point2.dimensionData, (o1, o2) => (o1 * point1Mass + o2 * point2Mass) / (point1Mass + point2Mass));
            return new EuclidianVector(NewPoints);
        }

        #region Equality Overloads
        /// <summary>
        /// Determines if vector a and vector b are equal
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns>True if vector a and b have the same number of dimensions and all of the dimensions are equal</returns>
        public static bool operator ==(EuclidianVector a, EuclidianVector b)
        {
            if (ReferenceEquals(a, b)) return true;
            if (ReferenceEquals(a, null) || ReferenceEquals(b, null)) return false;
            if (a.Dimensions != b.Dimensions) return false;
            for (int n = 0; n < a.Dimensions; n++)
            {
                if (a[n] != b[n]) return false;
            }
            return true;
        }

        /// <summary>
        /// Determins if vector a and b are inequal
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns>True if vector a and b have a different number of dimensions or if one or more of their dimensions are unequal to each other</returns>
        public static bool operator !=(EuclidianVector a, EuclidianVector b)
        {
            return !(a == b);
        }

        /// <summary>
        /// Determines if this vector is equal to object obj 
        /// </summary>
        /// <param name="obj"></param>
        /// <returns>True if the obj is a EuclidianVector with the same number of dimensions and all of its dimensions are equal to this vectors dimensions</returns>
        public override bool Equals(object obj)
        {
            EuclidianVector b = obj as EuclidianVector;
            return this == b;
        }

        /// <summary>
        /// Determines if this vector is equal to object obj 
        /// </summary>
        /// <param name="other"></param>
        /// <returns>True if the other vector has the same number of dimensions and all of its dimensions are equal to this vectors dimensions</returns>
        public bool Equals(EuclidianVector other)
        {
            return this == other;
        }
        #endregion

        /// <summary>
        /// Serves as a hash function for a particular type
        /// </summary>
        /// <returns>A hash code for the current EuclidianVector</returns>
        public override int GetHashCode()
        {
            return this.dimensionData.Aggregate(0, (Agrigate, next) => Agrigate ^ next.GetHashCode());
        }

        /// <summary>
        /// Gets a string that represents the Euclidian Vector's current state
        /// </summary>
        /// <returns>A string that contains the vector's dimension data seperated by commas</returns>
        public override string ToString()
        {
            StringBuilder bldr = new StringBuilder();
            foreach (decimal d in this.dimensionData)
            {
                bldr.Append(d + ", ");
            }
            return bldr.ToString().Trim(',', ' ');
        }
    }
}
